﻿#pragma once

#include "common.h"
#include <map>
#include <set>

// ANIP_TODO: make _pimpl for anip::Shader to hide <set>

namespace anip
{
	class Texture2D;

	class ShaderPart: public GLObject
	{
	public:
		enum class Type
		{
			Fragment = 0x8b30, // GL_FRAGMENT_SHADER,
			Vertex = 0x8b31, // GL_VERTEX_SHADER,
			Geometry = 0x8dd9, //GL_GEOMETRY_SHADER
		};

	private:
		Type _type;
		unsigned int _hshader;
		string _message;

	public:
		ShaderPart(Type type, const string& code);
		ShaderPart(const ShaderPart& obj) = delete;

		unsigned int id() const { return _hshader; }
		void finalize();
		Type type() const { return _type; };
		const string& message() const { return _message; }
	};

	class Shader : public GLObject
	{
	private:
		std::set<const ShaderPart*> _parts;
		std::map<string, unsigned int> _uniforms;
		std::map<string, unsigned int> _attribs;
		unsigned int _hprog;
		bool _linked = false;

		unsigned int _uniformLocation(const string& name);
		unsigned int _attribLocation(const string& name);

	public:
		Shader();
		Shader(const std::initializer_list<const ShaderPart*>& parts);
		Shader(const Shader& obj) = delete;

		unsigned int id() const { return _hprog; }
		void finalize();

		static Shader* load(const string& vert, const string& frag);

		unsigned int getAttribLocation(const string& name)
		{
			return _attribLocation(name);
		}

		void attach(const ShaderPart& part);
		void detach(const ShaderPart& part);
		int partCount() const { return _parts.size(); }

		void compile();
		void use() const;

		void set(const string& name, float value);
		void set(const string& name, double value);
		void set(const string& name, int value);
		void set(const string& name, const Texture2D &tex, int i);

		void set(const string& name, int value1, int value2);
		void set(const string& name, double value1, double value2);
		void set(const string& name, float value1, float value2);
		void set(const string& name, invec2 v);

		void set(const string& name, int value1, int value2, int value3);
		void set(const string& name, double value1, double value2, double value3);
		void set(const string& name, float value1, float value2, float value3);

		void set(const string& name, int value1, int value2, int value3, int value4);
		void set(const string& name, double value1, double value2, double value3, double value4);
		void set(const string& name, float value1, float value2, float value3, float value4);
		void set(const string& name, const color& c);

		void set(const string& name, inmat3 v);
	};
}